﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using Infrastructure.Logging;

namespace Infrastructure
{
    public static class Utils
    {
        private static readonly ILog log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType );

        public static Dictionary<string, string> RomeNumerals { get; private set; }

        static Utils ()
        {
            RomeNumerals = new Dictionary<string, string>();
            RomeNumerals.Add( "I", "01" );
            RomeNumerals.Add( "II", "02" );
            RomeNumerals.Add( "III", "03" );
            RomeNumerals.Add( "IV", "04" );
            RomeNumerals.Add( "V", "05" );
            RomeNumerals.Add( "VI", "06" );
            RomeNumerals.Add( "VII", "07" );
            RomeNumerals.Add( "VIII", "08" );
            RomeNumerals.Add( "IX", "09" );
            RomeNumerals.Add( "X", "10" );
        }

        /// <summary>
        /// Gets the base directory for this <see cref="AppDomain"/>.
        /// </summary>
        /// <value>The base directory path for the current <see cref="AppDomain"/>.</value>
        /// <remarks>
        /// <para>
        /// Gets the base directory for this <see cref="AppDomain"/>.
        /// </para>
        /// <para>
        /// The value returned may be either a local file path or a URI.
        /// </para>
        /// </remarks>
        public static string ApplicationBaseDirectory
        {
            get
            {
#if NETCF
				return System.IO.Path.GetDirectoryName(SystemInfo.EntryAssemblyLocation) + System.IO.Path.DirectorySeparatorChar;
#else
                return System.AppDomain.CurrentDomain.BaseDirectory;
#endif
            }
        }

        /// <summary>
        /// Convert a path into a fully qualified local file path.
        /// </summary>
        /// <param name="path">The path to convert.</param>
        /// <returns>The fully qualified path.</returns>
        /// <remarks>
        /// <para>
        /// Converts the path specified to a fully
        /// qualified path. If the path is relative it is
        /// taken as relative from the application base 
        /// directory.
        /// </para>
        /// <para>
        /// The path specified must be a local file path, a URI is not supported.
        /// </para>
        /// </remarks>
        public static string ConvertToFullPath ( string path )
        {
            if (path == null)
            {
                throw new ArgumentNullException( "path" );
            }

            string baseDirectory = "";
            try
            {
                string applicationBaseDirectory = ApplicationBaseDirectory;
                if (applicationBaseDirectory != null)
                {
                    // applicationBaseDirectory may be a URI not a local file path
                    var applicationBaseDirectoryUri = new Uri( applicationBaseDirectory );
                    if (applicationBaseDirectoryUri.IsFile)
                    {
                        baseDirectory = applicationBaseDirectoryUri.LocalPath;
                    }
                }
            }
            catch
            {
                // Ignore URI exceptions & SecurityExceptions from SystemInfo.ApplicationBaseDirectory
            }

            if (!string.IsNullOrEmpty( baseDirectory ))
            {
                // Note that Path.Combine will return the second path if it is rooted
                return Path.GetFullPath( Path.Combine( baseDirectory, path ) );
            }
            return Path.GetFullPath( path );
        }

        /// <summary>
        /// add post params to <see cref="HttpWebRequest"/>
        /// </summary>
        /// <param name="webRequest"></param>
        /// <param name="postParams"></param>
        public static void FillPostParams ( HttpWebRequest webRequest,string postParams )
        {
            var byteArray1 = Encoding.UTF8.GetBytes( postParams );
            webRequest.ContentType = "application/x-www-form-urlencoded";
            webRequest.ContentLength = byteArray1.Length;
            using (Stream stream = webRequest.GetRequestStream())
            {
                stream.Write( byteArray1, 0, byteArray1.Length );
                stream.Close();
            }
        }

        /// <summary>
        /// Build a new request with browser properties
        /// </summary>
        /// <param name="url"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        public static HttpWebRequest GetBrowserRequest ( string url, string method = "GET" )
        {
            var httpWebRequest = (HttpWebRequest)WebRequest.Create( url );
            httpWebRequest.Method = method;
            httpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.2)";
            httpWebRequest.Accept = "application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*";
            httpWebRequest.Headers[HttpRequestHeader.AcceptLanguage] = "en-us,en;q=0.5";
            httpWebRequest.Headers[HttpRequestHeader.AcceptEncoding] = "gzip,deflate";
            httpWebRequest.Headers[HttpRequestHeader.AcceptCharset] = "ISO-8859-1,utf-8;q=0.7,*;q=0.7";
            httpWebRequest.Headers[HttpRequestHeader.KeepAlive] = "115";

            httpWebRequest.KeepAlive = true;
            httpWebRequest.CookieContainer = new CookieContainer();
            return httpWebRequest;
        }

        /// <summary>
        /// Get response from url 
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string GetHtmlResponse ( string url )
        {
            var httpWebRequest = (HttpWebRequest)WebRequest.Create( url );
            httpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.2)";
            httpWebRequest.Accept = "application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*";
            httpWebRequest.Headers[HttpRequestHeader.AcceptLanguage] = "en-us,en;q=0.5";
            httpWebRequest.Headers[HttpRequestHeader.AcceptEncoding] = "gzip,deflate";
            httpWebRequest.Headers[HttpRequestHeader.AcceptCharset] = "ISO-8859-1,utf-8;q=0.7,*;q=0.7";
            httpWebRequest.Headers[HttpRequestHeader.KeepAlive] = "115";

            httpWebRequest.KeepAlive = true;
            httpWebRequest.CookieContainer = new CookieContainer();
            var webResponse = (HttpWebResponse)httpWebRequest.GetResponse();

            Stream responseStream = GetStreamForResponse( webResponse, 5000 );            
            string html;
            using (var sr = new StreamReader( responseStream, Encoding.UTF8 ))
            {
                html = sr.ReadToEnd();
            }
            return html;
        }

        /// <summary>
        /// Move files from <paramref name="sourceFolder"/> to <paramref name="destFolder"/>.
        /// </summary>
        /// <remarks>
        /// The move is executed async.
        /// <para>
        /// The folder is first renamed (and new folder is created for the original name) and then a 
        /// copy process is executed so that new files will not be copied.
        /// <div>the collection of files is optimized for large amount of files</div>
        /// <div>after the copy process is complete the tmp copy folder is deleted</div>
        /// <div>if the copy process was interupted, next time it will copy old files as well because of the unique name of the copy folder</div>
        /// </para>
        /// </remarks>
        /// <param name="sourceFolder">the folder FROM with to copy the files</param>
        /// <param name="destFolder">the folder TO with to copy the files</param>
        public static void MoveFiles ( string sourceFolder, string destFolder )
        {
            if (!Directory.Exists( sourceFolder ))
                return;

            if (!Directory.Exists( destFolder ))
                Directory.CreateDirectory( destFolder );

            log.Info( string.Format( "Move the source folder by rename so it will be easier to move files..." ) );
            var tmpSourceFolder = string.Format( "{0}_copy_{1}_{2}", sourceFolder, DateTime.Now.ToString( "ddMM" ), Guid.NewGuid().ToString().Substring( 0, 5 ) );
            Directory.Move( sourceFolder, tmpSourceFolder );
            Directory.CreateDirectory( sourceFolder );

            log.Info( string.Format( "Get the temp copy folders, include previous if found..." ) );
            var folderParent = new DirectoryInfo( tmpSourceFolder ).Parent;
            string[] tmpCopyFolders = folderParent == null
                                           ? new[] { tmpSourceFolder }
                                           : Directory.GetDirectories( folderParent.FullName, Path.GetFileName( sourceFolder ) + "_copy*" );

            log.Info( string.Format( "Start async files move for each temp folder [Count: {0}]", tmpCopyFolders.Length ) );
            foreach (var tmpFolderOuter in tmpCopyFolders)
            {
                string tmpFolder = tmpFolderOuter;
                ThreadPool.QueueUserWorkItem( state => MoveFilesFromFolders( tmpFolder, destFolder ) );
            }
        }

        /// <summary>
        /// Move all the files from <paramref name="tmpFolder"/> to <paramref name="destFolder"/>.
        /// <div>optimized for large amount of files</div>
        /// </summary>
        /// <param name="tmpFolder">the folder FROM with to copy the files</param>
        /// <param name="destFolder">the folder TO with to copy the files</param>
        private static void MoveFilesFromFolders ( string tmpFolder, string destFolder )
        {
            try
            {
                log.Info( string.Format( "Start move files... [{0}] -> [{1}]", tmpFolder, destFolder ) );

                var queuedFiles = FileSystemUtils.GetFiles( tmpFolder, 500 );
                var queuedFolders = FileSystemUtils.GetDirectories( tmpFolder, 0, 100 );
                while (queuedFiles.Count > 0 || queuedFolders.Count > 0)
                {
                    //move fils from folder
                    log.Info( string.Format( "Start files copy [Count: {0}]", queuedFiles.Count ) );
                    foreach (var filePath in queuedFiles)
                    {
                        try
                        {
                            string destFilePath = Path.Combine( destFolder, Path.GetFileName( filePath ) );
                            File.Move( filePath, destFilePath );
                        }
                        catch (Exception ex)
                        {
                            log.Info( string.Format( "Move file failed" ), ex );
                        }
                    }

                    //move subfodlers
                    log.Info( string.Format( "Start folder copy [Count: {0}]", queuedFolders.Count ) );
                    foreach (var folderPath in queuedFolders)
                    {
                        try
                        {
                            string destFolderPath = Path.Combine( destFolder, Path.GetFileName( folderPath ) );
                            if (Directory.Exists( destFolderPath ))
                            {
                                MoveFilesFromFolders( folderPath, destFolderPath );
                            }
                            else
                            {
                                Directory.Move( folderPath, destFolderPath );
                            }
                        }
                        catch (Exception ex)
                        {
                            log.Info( string.Format( "Move file failed" ), ex );
                        }
                    }

                    log.Info( string.Format( "Get next bulk of files to copy..." ) );
                    queuedFiles = FileSystemUtils.GetFiles( tmpFolder, 500 );
                    queuedFolders = FileSystemUtils.GetDirectories( tmpFolder, 0, 100 );
                }

                Directory.Delete( tmpFolder );
                log.Info( string.Format( "Finished!" ) );
            }
            catch (Exception ex)
            {
                log.Info( string.Format( string.Format( "Failed to move files [{0}] -> [{1}]: {2}", tmpFolder, destFolder, ex.Message ) ) );
            }
        }

        public static void CallWithTimeout ( Action action, int timeoutMilliseconds )
        {
            Thread threadToKill = null;
            Action wrappedAction = () =>
            {
                threadToKill = Thread.CurrentThread;
                action();
            };

            IAsyncResult result = wrappedAction.BeginInvoke( null, null );
            if (result.AsyncWaitHandle.WaitOne( timeoutMilliseconds ))
            {
                wrappedAction.EndInvoke( result );
            }
            else
            {
                threadToKill.Abort();
                throw new TimeoutException();
            }
        }

        
        private static Stream GetStreamForResponse ( HttpWebResponse webResponse, int readTimeOut )
        {
            Stream stream;
            switch (webResponse.ContentEncoding.ToUpperInvariant())
            {
                case "GZIP":
                    stream = new GZipStream( webResponse.GetResponseStream(), CompressionMode.Decompress );
                    break;
                case "DEFLATE":
                    stream = new DeflateStream( webResponse.GetResponseStream(), CompressionMode.Decompress );
                    break;

                default:
                    stream = webResponse.GetResponseStream();
                    stream.ReadTimeout = readTimeOut;
                    break;
            }
            return stream;
        }
    }
}